#include "overlay.h"
#include "frameui/image.h"
#include "painter.h"
#include <windowsx.h>
#include <commctrl.h>

#define DISPLAY_SCROLL        1
enum {
  RESIZE_LEFT     = 0x01,
  RESIZE_TOP      = 0x02,
  RESIZE_RIGHT    = 0x04,
  RESIZE_BOTTOM   = 0x08,

  RESIZE_SCROLL   = 0x10,

  RESIZE_BORDER   = 8,
};

void AddText(SharedHeader* hdr, wchar_t const* text, int len)
{
  int rlen = wcslen(hdr->replyText);
  int slen = hdr->replyCaret - hdr->replySelStart; if (slen < 0) slen = -slen;
  if (rlen + len - slen > 255) len = 255 - rlen + slen;
  if (len <= 0) len = 0;
  int s0 = (hdr->replyCaret < hdr->replySelStart ? hdr->replyCaret : hdr->replySelStart);
  memmove(hdr->replyText + s0 + len, hdr->replyText + s0 + slen, sizeof(wchar_t) * (rlen - slen - s0));
  memcpy(hdr->replyText + s0, text, sizeof(wchar_t) * len);
  hdr->replyText[rlen + len - slen] = 0;
  hdr->replyCaret = hdr->replySelStart = s0 + len;
}
OverlayWindow* OverlayWindow::instance = NULL;
DWORD WINAPI OverlayWindow::HookThread(LPVOID param)
{
  HMODULE thisModule = GetModuleHandle(NULL);
  HHOOK hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, thisModule, 0);
  HHOOK hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, thisModule, 0);
  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0) != 0)
    ;
  UnhookWindowsHookEx(hMouseHook);
  UnhookWindowsHookEx(hKeyboardHook);
  return 0;
}
LRESULT CALLBACK OverlayWindow::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) 
{
  if (nCode < 0) return CallNextHookEx(NULL, nCode, wParam, lParam);

  if (instance && instance->onMouseEvent(wParam, (MSLLHOOKSTRUCT*) lParam))
    return TRUE;

  return CallNextHookEx(NULL, nCode, wParam, lParam);
}
LRESULT CALLBACK OverlayWindow::KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) 
{
  if (nCode < 0) return CallNextHookEx(NULL, nCode, wParam, lParam);

  if (instance && instance->onKeyEvent(wParam, (KBDLLHOOKSTRUCT*) lParam))
    return TRUE;

  return CallNextHookEx(NULL, nCode, wParam, lParam);
}
OverlayWindow::OverlayWindow(OverlayPainter* pPainter, bool interactive)
  : painter(pPainter)
  , data(pPainter->getData())
  , hBitmapDC(NULL)
  , hBitmap(NULL)
  , resizing(0)
  , scrollTop(0)
  , display(0)
  , showReply(false)
  , hoverReply(false)
  , hHookThread(NULL)
{
  instance = this;
  if (WNDCLASSEX* wcx = createclass(L"OverlayWndClass"))
  {
    wcx->hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
    wcx->hCursor = LoadCursor(NULL, IDC_ARROW);
    RegisterClassEx(wcx);
  }
  create(0, 0, 200, 100, L"KappaUI Overlay", WS_POPUP, WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT);
  setInteractive(interactive);
  ShowWindow(hWnd, SW_SHOW);
}
OverlayWindow::~OverlayWindow()
{
  setInteractive(false);
  instance = NULL;
  if (hBitmap) DeleteObject(hBitmap);
  if (hBitmapDC) DeleteDC(hBitmapDC);
}
void OverlayWindow::setInteractive(bool interactive)
{
  if (interactive)
  {
    if (hHookThread == NULL)
      hHookThread = CreateThread(NULL, 0, HookThread, NULL, 0, &hookThreadId);
  }
  else
  {
    if (hHookThread)
    {
      PostThreadMessage(hookThreadId, WM_QUIT, 0, 0);
      WaitForSingleObject(hHookThread, INFINITE);
      CloseHandle(hHookThread);
      hHookThread = NULL;
    }
  }
}

void OverlayWindow::update(HDC hPassedDC)
{
  SharedData::Lock lock(data, 100);
  if (!lock.locked()) return;

  SharedHeader* hdr = data->header();
  FrameHeader* frame = data->frame();

  if (hdr->hidden)
  {
    hideWindow();
    return;
  }
  else if (!IsWindowVisible(hWnd))
    showWindow(true);
  SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_FRAMECHANGED);

  Image image(frame->width, frame->height);
  memcpy(image.bits(), data->bits(frame->offset), sizeof(uint32) * frame->width * frame->height);

  int replyHeight = 0;
  ReplyHeader* reply = data->reply();
  if ((showReply || hoverReply) && reply->width && reply->height)
  {
    replyHeight += reply->height - 2;
    int y = frame->height - reply->height;
    Image iReply(reply->width, reply->height, data->bits(reply->offset));
    image.blt(0, y, &iReply);
    if ((GetTickCount() % 1000) < 500)
      image.fill(0xFFFFFFFF, hdr->replyPos[hdr->replyCaret], (y + frame->height - 18) / 2, 1, 18);
  }

  ScrollHeader* scroll = data->scroll();
  if (scroll->width && scroll->height && hdr->maxScroll > 0)
  {
    Image iScroll(scroll->width, scroll->height, data->bits(scroll->offset));
    int height = frame->height - scroll->barTop - scroll->barBottom - replyHeight;
    int content = frame->height - frame->insetTop - frame->insetBottom - replyHeight;
    int maxScroll = hdr->maxScroll + content;
    int barHeight = height * content / maxScroll;
    int barOffset = height * hdr->scrollPos / maxScroll;
    RECT src;
    src.left = frame->width - scroll->barRight - scroll->width;
    src.right = frame->width - scroll->barRight;
    src.top = scroll->barTop + barOffset;
    src.bottom = src.top + barHeight;
    BLTInfo blt(&iScroll);
    blt.x = frame->width - scroll->barRight - scroll->width;
    blt.y = scroll->barTop + barOffset;
    blt.dstW = scroll->width;
    blt.dstH = barHeight;
    if (!(display & DISPLAY_SCROLL))
      blt.modulate = 0x99FFFFFF;
    image.blt(blt);
  }

  int y0 = frame->insetTop;
  int y1 = frame->height - frame->insetBottom - replyHeight + 8;
  int x0 = frame->insetLeft;
  int y = y0 - hdr->scrollPos - replyHeight;
  for (int i = 0; i < hdr->numLines; i++)
  {
    LineHeader* line = data->line(i);
    y += line->insetTop;
    int top = 0;
    int bottom = line->height;
    if (y + top < y0) top = y0 - y;
    if (y + bottom > y1) bottom = y1 - y;
    if (bottom > top)
    {
      Image iLine(line->width, line->height, data->bits(line->offset));
      BLTInfo blt(&iLine);
      blt.srcY = top;
      blt.srcH = bottom - top;
      blt.x = x0;
      blt.y = y + top;
      blt.dstW = line->width;
      blt.dstH = bottom - top;
      image.blt(blt);
    }
    y += line->height + line->insetBottom;
  }

  HDC hDC = hPassedDC;
  if (hDC == NULL)
    hDC = GetDC(hWnd);

  if (hBitmapDC == NULL)
    hBitmapDC = CreateCompatibleDC(hDC);
  if (hBitmap != NULL && (image.width() != bitmapWidth || image.height() != bitmapHeight))
  {
    DeleteObject(hBitmap);
    hBitmap = NULL;
  }
  if (hBitmap == NULL)
  {
    hBitmap = CreateCompatibleBitmap(hDC, image.width(), image.height());
    bitmapWidth = image.width();
    bitmapHeight = image.height();
  }
  image.fillBitmap(hBitmap, hDC);
  SelectObject(hBitmapDC, hBitmap);

  if (hPassedDC == NULL)
  {
    POINT pt = {hdr->left, hdr->top};
    SIZE sz = {hdr->right - hdr->left, hdr->bottom - hdr->top};
    POINT zero = {0, 0};
    BLENDFUNCTION blend;
    blend.BlendOp = AC_SRC_OVER;
    blend.BlendFlags = 0;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;
    UpdateLayeredWindow(hWnd, NULL, &pt, &sz, hBitmapDC, &zero, 0, &blend, ULW_ALPHA);
  }
  else
    BitBlt(hDC, 0, 0, image.width(), image.height(), hBitmapDC, 0, 0, SRCCOPY);

  if (hPassedDC == NULL)
    ReleaseDC(hWnd, hDC);
}
bool OverlayWindow::onMouseEvent(unsigned long msg, MSLLHOOKSTRUCT const* mh)
{
  SharedHeader* hdr = data->lock(100);
  if (!hdr) return false;

  bool result = false;
  POINT pt = mh->pt;
  ScreenToClient(hdr->hWnd, &pt);
  display = 0;

  if (resizing || pt.x >= hdr->left && pt.x < hdr->right && pt.y >= hdr->top && pt.y < hdr->bottom)
  {
    FrameHeader* frame = data->frame();
    ScrollHeader* scroll = data->scroll();
    ReplyHeader* reply = data->reply();
    int replyHeight = 0;
    if ((showReply || hoverReply) && reply->width && reply->height)
      replyHeight += reply->height - 2;
    if (pt.x >= hdr->left + (frame->width - scroll->barRight - scroll->width) * (hdr->right - hdr->left) / frame->width &&
        pt.x < hdr->left + (frame->width - scroll->barRight) * (hdr->right - hdr->left) / frame->width &&
        pt.y >= hdr->top + scroll->barTop * (hdr->bottom - hdr->top) / frame->height &&
        pt.y < hdr->top + (frame->height - scroll->barBottom) * (hdr->bottom - hdr->top) / frame->height)
      display |= DISPLAY_SCROLL;
    if (msg == WM_LBUTTONDOWN)
    {
      resizing = 0;
      if (pt.x >= hdr->left && pt.x < hdr->right && pt.y >= hdr->top && pt.y < hdr->bottom)
      {
        if (scroll->width && scroll->height && hdr->maxScroll >= 0)
        {
          int height = frame->height - scroll->barTop - scroll->barBottom - replyHeight;
          int content = frame->height - frame->insetTop - frame->insetBottom - replyHeight;
          int maxScroll = hdr->maxScroll + content;
          int barHeight = height * content / maxScroll;
          int barOffset = height * hdr->scrollPos / maxScroll;
          RECT rcScroll;
          rcScroll.left = frame->width - scroll->barRight - scroll->width;
          rcScroll.top = scroll->barTop + barOffset;
          rcScroll.right = frame->width - scroll->barRight;
          rcScroll.bottom = rcScroll.top + barHeight;
          rcScroll.left = hdr->left + rcScroll.left * (hdr->right - hdr->left) / frame->width;
          rcScroll.top = hdr->top + rcScroll.top * (hdr->bottom - hdr->top) / frame->height;
          rcScroll.right = hdr->left + rcScroll.right * (hdr->right - hdr->left) / frame->width;
          rcScroll.bottom = hdr->top + rcScroll.bottom * (hdr->bottom - hdr->top) / frame->height;
          if (pt.x >= rcScroll.left && pt.x < rcScroll.right &&
              pt.y >= rcScroll.top && pt.y < rcScroll.bottom)
          {
            resizing = RESIZE_SCROLL;
            scrollTop = rcScroll.top;
          }
        }
        if (resizing == 0)
        {
          if (pt.x < hdr->left + RESIZE_BORDER) resizing |= RESIZE_LEFT;
          if (pt.y < hdr->top + RESIZE_BORDER) resizing |= RESIZE_TOP;
          if (pt.x >= hdr->right - RESIZE_BORDER) resizing |= RESIZE_RIGHT;
          if (pt.y >= hdr->bottom - RESIZE_BORDER) resizing |= RESIZE_BOTTOM;
        }
        if (resizing == 0)
          resizing = RESIZE_LEFT | RESIZE_TOP | RESIZE_RIGHT | RESIZE_BOTTOM;
        resizePrev = pt;
      }
    }
    else if (msg == WM_LBUTTONUP)
      resizing = 0;
    else if (msg == WM_MOUSEMOVE && resizing)
    {
      int dx = pt.x - resizePrev.x;
      int dy = pt.y - resizePrev.y;
      if (resizing & RESIZE_LEFT) hdr->left += dx;
      if (resizing & RESIZE_TOP) hdr->top += dy;
      if (resizing & RESIZE_RIGHT) hdr->right += dx;
      if (resizing & RESIZE_BOTTOM) hdr->bottom += dy;
      if (resizing & RESIZE_SCROLL)
      {
        scrollTop += dy;
        int y = (scrollTop - hdr->top) * frame->height /
          (hdr->bottom - hdr->top) - scroll->barTop;
        int height = frame->height - scroll->barTop - scroll->barBottom - replyHeight;
        int content = frame->height - frame->insetTop - frame->insetBottom - replyHeight;
        int maxScroll = hdr->maxScroll + content;
        hdr->scrollPos = y * maxScroll / height;
        if (hdr->scrollPos < 0) hdr->scrollPos = 0;
        if (hdr->scrollPos > hdr->maxScroll) hdr->scrollPos = hdr->maxScroll;
      }
      painter->updateRect();
      resizePrev = pt;
    }
    else if (msg == WM_MOUSEWHEEL)
    {
      hdr->scrollPos -= GET_WHEEL_DELTA_WPARAM(mh->mouseData) * 72 / WHEEL_DELTA;
      if (hdr->scrollPos < 0) hdr->scrollPos = 0;
      if (hdr->scrollPos > hdr->maxScroll) hdr->scrollPos = hdr->maxScroll;
    }
    if (resizing & RESIZE_SCROLL)
      display |= DISPLAY_SCROLL;
    if (pt.y >= hdr->bottom - reply->height * (hdr->bottom - hdr->top) / frame->height)
    {
      if (msg == WM_LBUTTONDOWN)
        showReply = true;
      hoverReply = true;
    }
    else
      hoverReply = false;
    result = (msg != WM_MOUSEMOVE);
  }
  else if (msg == WM_LBUTTONDOWN)
  {
    showReply = false;
    hoverReply = false;
  }
  else
    hoverReply = false;

  data->unlock();

  return result;
}
bool OverlayWindow::onKeyEvent(unsigned long msg, KBDLLHOOKSTRUCT const* kb)
{
  if (msg == VK_F4 && (GetAsyncKeyState(VK_MENU) & 0x8000))
    return false;

  SharedData::Lock locker(data, 100);
  if (!locker.locked()) return false;

  SharedHeader* hdr = data->header();
  ReplyHeader* reply = data->reply();

  int hk = kb->vkCode;
  if (GetAsyncKeyState(VK_MENU) & 0x8000)
    hk |= (HOTKEYF_ALT << 8);
  if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
    hk |= (HOTKEYF_CONTROL << 8);
  if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
    hk |= (HOTKEYF_SHIFT << 8);

  if (msg == WM_KEYDOWN && hk == hdr->replyHotkey)
    showReply = !showReply;
  else if (showReply)
  {
    if (msg == WM_KEYDOWN)
    {
      switch (kb->vkCode)
      {
      case VK_ESCAPE:
        hdr->replyText[0] = 0;
        hdr->replyCaret = hdr->replySelStart = hdr->replyScroll = 0;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        showReply = false;
        break;
      case VK_RETURN:
        PostMessage(hdr->hWndLauncher, WM_POSTREPLY, 0, 0);
        break;
      case VK_BACK:
        if (hdr->replyCaret == hdr->replySelStart && hdr->replyCaret > 0)
          hdr->replySelStart = hdr->replyCaret - 1;
        AddText(hdr, L"", 0);
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_DELETE:
        if (hdr->replyCaret == hdr->replySelStart && hdr->replyText[hdr->replyCaret])
          hdr->replySelStart = hdr->replyCaret + 1;
        AddText(hdr, L"", 0);
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_LEFT:
        if (hdr->replyCaret > 0)
          hdr->replyCaret--;
        if (!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
          hdr->replySelStart = hdr->replyCaret;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_RIGHT:
        if (hdr->replyText[hdr->replyCaret])
          hdr->replyCaret++;
        if (!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
          hdr->replySelStart = hdr->replyCaret;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_HOME:
        hdr->replyCaret = 0;
        if (!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
          hdr->replySelStart = hdr->replyCaret;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_END:
        hdr->replyCaret = wcslen(hdr->replyText);
        if (!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
          hdr->replySelStart = hdr->replyCaret;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      default:
        {
          wchar_t buf[16];
          BYTE state[256];
          GetKeyboardState(state);
          int num = ToUnicode(kb->vkCode, kb->scanCode, state, buf, 16, 0);
          for (int i = 0; i < num; i++)
            if (iswprint(buf[i]))
              AddText(hdr, buf + i, 1);
          if (num)
            PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        }
      }
    }
  }
  else
    return false;

  return true;
}
